package com.fjec.base.service.impl;

import com.alibaba.cloudapi.sdk.constant.SdkConstant;
import com.alibaba.cloudapi.sdk.model.ApiResponse;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.fjec.base.entity.SysAnnex;
import com.fjec.base.mapper.SysAnnexMapper;
import com.fjec.base.service.ISysAnnexService;
import com.fjec.base.view.AnnexVO;
import com.fjec.common.Const;
import com.fjec.common.aliyun.api.BankCardClient;
import com.fjec.common.aliyun.api.BusinessLicenseClient;
import com.fjec.common.aliyun.api.IdCardClient;
import com.fjec.common.aliyun.api.view.BankCardVO;
import com.fjec.common.aliyun.api.view.BusinessLicenseVO;
import com.fjec.common.aliyun.api.view.IdCardVO;
import com.fjec.common.enums.EnumSysCode;
import com.fjec.common.protocol.JsonResult;
import com.fjec.common.util.*;

import com.fjec.common.view.AnnexBizItem;
import lombok.extern.slf4j.Slf4j;

import net.coobird.thumbnailator.Thumbnails;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 001.附件表 服务实现类
 * </p>
 *
 * @author admin
 * @since 2020-08-31
 */
@Slf4j
@Service
@Transactional
public class SysAnnexServiceImpl extends ServiceImpl<SysAnnexMapper, SysAnnex> implements ISysAnnexService {

    @Resource
    private SequenceIdGenerator idGenerator;

    @Resource
    private SysAnnexMapper mapper;

    @Resource(name = "storeAliOSSUtil")
    private AliOSSUtil storeAliOSSUtil;

    @Resource(name = "secretAliOSSUtil")
    private AliOSSUtil secretAliOSSUtil;

    @Resource
    private IdCardClient idCardClient;

    @Resource
    private BusinessLicenseClient businessLicenseClient;

    @Resource
    private BankCardClient bankCardClient;

    @Override
    public JsonResult storeFileUpload(MultipartFile multipartFile, Integer bizType) throws Exception {
        String fileName = multipartFile.getOriginalFilename();
        String contentType = multipartFile.getContentType();
        byte[] bytes = null;
        // 缩放图片
        if (isChangeSize && Const.FILE_TYPE_IMAGE.contains(bizType)) {
            bytes = changeSize(multipartFile.getInputStream());
        } else {
            bytes = FileUtil.getInputStreamBytes(multipartFile.getInputStream());
        }
        AnnexVO view = this.storeUpload(new ByteArrayInputStream(bytes), contentType, fileName, bizType, 1);
        if (view != null) {
            JsonResult result = JsonResult.success().data(view).build();
            return result;
        } else {
            return JsonResult.failure()
                    .code(EnumSysCode.ERR_0028.getCode())
                    .message(EnumSysCode.ERR_0028.getMsg())
                    .build();
        }
    }

    @Override
    public JsonResult secretFileUpload(MultipartFile multipartFile, Integer bizType) throws Exception {
        String fileName = multipartFile.getOriginalFilename();
        String contentType = multipartFile.getContentType();
        byte[] bytes = null;
        // 缩放图片
//        if (isChangeSize && Const.FILE_TYPE_IMAGE.contains(bizType)) {
//            bytes = changeSize(multipartFile.getInputStream());
//        } else {
//            bytes = FileUtil.getInputStreamBytes(multipartFile.getInputStream());
//        }

        bytes = this.compressPicForScale(multipartFile.getBytes(), 2048L);
        // 获取业务数据
        Map<String, Object> mapData = null;
        if (Const.GET_DATA_TYPE.contains(bizType)) {
            mapData = this.getBizData(new ByteArrayInputStream(bytes), bizType);
            if (mapData == null) {
                return JsonResult.failure()
                        .code(EnumSysCode.ERR_0027.getCode())
                        .message(EnumSysCode.ERR_0027.getMsg())
                        .build();
            }
        }
        AnnexVO view = this.secretUpload(new ByteArrayInputStream(bytes), contentType, fileName, bizType, mapData, 1);
        if (view != null) {
            return JsonResult.success()
                    .data(view)
                    .build();
        } else {
            return JsonResult.failure()
                    .code(EnumSysCode.ERR_0028.getCode())
                    .message(EnumSysCode.ERR_0028.getMsg())
                    .build();
        }
    }

    /**
     * 根据指定大小压缩图片
     *
     * @param imageBytes  源图片字节数组
     * @param desFileSize 指定图片大小，单位kb
     * @return 压缩质量后的图片字节数组
     */
    public byte[] compressPicForScale(byte[] imageBytes, long desFileSize) throws IOException {
        if (imageBytes == null || imageBytes.length <= 0 || imageBytes.length < desFileSize * 1024) {
            return imageBytes;
        }
        long srcSize = imageBytes.length;
        double accuracy = getAccuracy(srcSize / 1024);

        while (imageBytes.length > desFileSize * 1024) {
            ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
            java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(imageBytes.length);
            Thumbnails.of(inputStream)
                    .scale(accuracy)
                    .outputQuality(accuracy)
                    .toOutputStream(outputStream);
            imageBytes = outputStream.toByteArray();
        }
        return imageBytes;
    }

    /**
     * 自动调节精度(经验数值)
     *
     * @param size 源图片大小
     * @return 图片压缩质量比
     */
    private double getAccuracy(long size) {
        double accuracy;
        if (size < 900) {
            accuracy = 0.85;
        } else if (size < 2047) {
            accuracy = 0.6;
        } else if (size < 3275) {
            accuracy = 0.44;
        } else {
            accuracy = 0.4;
        }
        return accuracy;
    }


    @Override
    public JsonResult storeBase64FileUpload(String base64, String fileName, Integer bizType, Integer delFlag) throws Exception {
        byte[] bytes = Base64Util.decode2Bytes(base64);
        // 缩放图片
        if (isChangeSize) {
            bytes = changeSize(new ByteArrayInputStream(bytes));
        }
        AnnexVO view = this.storeUpload(new ByteArrayInputStream(bytes), "1", fileName, bizType, delFlag);
        if (view != null) {
            return JsonResult.success()
                    .data(view)
                    .build();
        } else {
            return JsonResult.failure()
                    .code(EnumSysCode.ERR_0028.getCode())
                    .message(EnumSysCode.ERR_0028.getMsg())
                    .build();
        }
    }

    @Override
    public JsonResult secretBase64FileUpload(String base64, String fileName, Integer bizType, Integer delFlag) throws Exception {
        byte[] bytes = Base64Util.decode2Bytes(base64);
        // 缩放图片
        if (isChangeSize) {
            bytes = changeSize(new ByteArrayInputStream(bytes));
        }
        // 获取业务数据
        Map<String, Object> mapData = null;
        if (Const.GET_DATA_TYPE.contains(bizType)) {
            mapData = this.getBizData(new ByteArrayInputStream(bytes), bizType);
            if (mapData == null) {
                return JsonResult.failure()
                        .code(EnumSysCode.ERR_0027.getCode())
                        .message(EnumSysCode.ERR_0027.getMsg())
                        .build();
            }
        }
        AnnexVO view = this.secretUpload(new ByteArrayInputStream(bytes), "1", fileName, bizType, mapData, delFlag);
        if (view != null) {
            return JsonResult.success()
                    .data(view)
                    .build();
        } else {
            return JsonResult.failure()
                    .code(EnumSysCode.ERR_0028.getCode())
                    .message(EnumSysCode.ERR_0028.getMsg())
                    .build();
        }
    }

    @Override
    public Map<String, Object> rpcStoreBase64FileUpload(String file, String fileName, Integer bizType, Long bizId, Integer bizCode,
                                                        Integer delFlag) throws Exception {
        byte[] bytes = Base64Util.decode2Bytes(file);
        // 缩放图片
        if (isChangeSize) {
            bytes = changeSize(new ByteArrayInputStream(bytes));
        }
        String contentType = "1";
        Map<String, String> map = storeAliOSSUtil.uploadFile(new ByteArrayInputStream(bytes), contentType, fileName);
        AnnexVO view = this.saveEntity(map, contentType, fileName, bizType, bizId, bizCode, null, delFlag);
        Map<String, Object> retMap = new HashMap<>();
        if (view == null) {
            return null;
        } else {
            retMap.put("id", view.getId());
            retMap.put("url", view.getUrl());
            return retMap;
        }
    }

    @Override
    public Map<String, Object> rpcSecretBase64FileUpload(String file, String fileName, Integer bizType, Long bizId, Integer bizCode, Integer delFlag) throws Exception {
        byte[] bytes = Base64Util.decode2Bytes(file);
        // 缩放图片
        if (isChangeSize) {
            bytes = changeSize(new ByteArrayInputStream(bytes));
        }
        // 获取业务数据
        Map<String, Object> mapData = null;
        if (Const.GET_DATA_TYPE.contains(bizType)) {
            mapData = this.getBizData(new ByteArrayInputStream(bytes), bizType);
            if (mapData == null) {
                return null;
            }
        }
        String contentType = "1";
        Map<String, String> map = secretAliOSSUtil.uploadFile(new ByteArrayInputStream(bytes), contentType, fileName);
        AnnexVO view = this.saveEntity(map, contentType, fileName, bizType, bizId, bizCode, mapData, delFlag);
        Map<String, Object> retMap = new HashMap<>();
        if (view == null) {
            return null;
        } else {
            retMap.put("id", view.getId());
            retMap.put("url", view.getUrl());
            return retMap;
        }
    }

    @Override
    public JsonResult updateData(Long id, Long bizId, Integer bizType, Integer bizCode, Integer sort) {
        SysAnnex annex = this.getById(id);
        annex.setUpdateTime(LocalDateTime.now());
        if (bizId != null) {
            annex.setBizId(bizId);
        }
        if (bizType != null) {
            annex.setBizType(bizType);
        }
        if (bizCode != null) {
            annex.setBizCode(bizCode);
        }
        if (sort != null) {
            annex.setSort(sort);
        }
//        if (bizData != null && !bizData.isEmpty()) {
//            annex.setBizData(bizData);
//        }
        annex.setDelFlag(0);
        this.updateById(annex);
        return JsonResult.SUCCESS;
    }

    @Override
    public JsonResult logicDelete(Long id) {
        SysAnnex annex = this.getById(id);
        annex.setUpdateTime(LocalDateTime.now());
        annex.setDelFlag(1);
        this.updateById(annex);
        return JsonResult.SUCCESS;
    }

    @Override
    public List<AnnexVO> getAnnex(Integer bizType, Long bizId, Integer bizCode) throws Exception {
        QueryWrapper<SysAnnex> wrapper = new QueryWrapper<>();
        wrapper.eq("bizType", bizType)
                .eq("delFlag", 0);
        if (bizId != null && bizId != 0) {
            wrapper.eq("bizId", bizId);
        }
        if (bizCode != null && bizCode != 0) {
            wrapper.eq("bizCode", bizCode);
        }
        wrapper.orderByAsc("sort");
        List<SysAnnex> entityList = mapper.selectList(wrapper);
        List<AnnexVO> resultList = new ArrayList<>();
        for (SysAnnex entity : entityList) {
            AnnexVO view = new AnnexVO();
            BeanUtils.copyProperties(view, entity);
            resultList.add(view);
        }
        return resultList;
    }

    @Override
    public void getSecretOSSFile(Long id, HttpServletResponse res) throws Exception {
        SysAnnex entity = mapper.selectById(id);
        if (entity != null) {
            res.setContentType("application/octet-stream");
            res.addHeader("filename", entity.getFileName());
            res.addHeader("Access-Control-Expose-Headers", "filename");
            byte[] bytes = secretAliOSSUtil.getObject(entity.getStoreLocation());
            OutputStream out = res.getOutputStream();
            out.write(bytes, 0, bytes.length);
            out.flush();
        }
    }

    @Override
    public String getOneSecretOSSFileBase64(Long bizId, Integer bizType) {
        String result = "";
        QueryWrapper<SysAnnex> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("bizType", bizType)
                .eq("bizId", bizId)
                .eq("DelFlag", 0)
                .orderByAsc("Sort");
        List<SysAnnex> entityList = mapper.selectList(queryWrapper);
        if (entityList != null && !entityList.isEmpty()) {
            SysAnnex entity = entityList.get(0);
            if (entity != null) {
                byte[] bytes = secretAliOSSUtil.getObject(entity.getStoreLocation());
                result = Base64Util.encode(bytes);
            }
        }
        return result;
    }

    /**
     * 更新附件为可用
     *
     * @return
     */
    @Override
    public int setAnnexAvailable(List<AnnexBizItem> itemList) {
        int ret = 0;
        for (AnnexBizItem item : itemList) {
            UpdateWrapper<SysAnnex> updateWrapper = new UpdateWrapper<>();
            updateWrapper.eq("id", item.getAnnexId())
                    .eq("bizType", item.getBizType());
            SysAnnex entity = new SysAnnex();
//            entity.setBizType(item.getBizType());
            entity.setBizId(item.getBizId());
            if (item.getBizCode() != -1) {
                entity.setBizCode(item.getBizCode());
            }
            entity.setDelFlag(0);
            ret += mapper.update(entity, updateWrapper);
        }
        return ret;
    }

    @Override
    public List<SysAnnex> getAnnexForDelete(int limit, int days) {
        return mapper.getAnnexForDelete(limit, days);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void physicalDeleteAnnex(SysAnnex entity) {
        boolean isDelete;
        if (Const.SECRET_BIZ_TYPE.contains(entity.getBizType())) {
            isDelete = secretAliOSSUtil.deleteFile(entity.getStoreLocation());
        } else {
            isDelete = storeAliOSSUtil.deleteFile(entity.getStoreLocation());
        }
        if (isDelete) {
            mapper.deleteById(entity.getId());
        }
    }

    @Override
    public String getOSSFileBase64ById(Long id) {
        String result = "";
        SysAnnex entity = mapper.selectById(id);
        if (entity != null) {
            byte[] bytes = secretAliOSSUtil.getObject(entity.getStoreLocation());
            result = Base64Util.encode(bytes);
        }
        return result;
    }

    @Override
    public Map<String, Object> getOSSFileBase64ByIdMap(Long id) {
        Map<String, Object> resultMap = new HashMap<>();
        SysAnnex entity = mapper.selectById(id);
        if (entity != null) {
            byte[] bytes = secretAliOSSUtil.getObject(entity.getStoreLocation());
            resultMap.put("base64", Base64Util.encode(bytes));
            resultMap.put("fileName", entity.getFileName());
        }
        return resultMap;
    }

    /**
     * 上传到fjstore OSS上
     *
     * @param inputStream
     * @param contentType
     * @param fileName
     * @param bizType
     * @return
     * @throws Exception
     */
    private AnnexVO storeUpload(InputStream inputStream, String contentType, String fileName,
                                Integer bizType, Integer delFlag) throws Exception {
        Map<String, String> map = storeAliOSSUtil.uploadFile(inputStream, contentType, fileName);
        AnnexVO view = saveEntity(map, contentType, fileName, bizType, null, delFlag);
        return view;
    }

    /**
     * 上传到fjsecret OSS上
     *
     * @param inputStream
     * @param contentType
     * @param fileName
     * @param bizType
     * @param bizData
     * @return
     * @throws Exception
     */
    private AnnexVO secretUpload(InputStream inputStream, String contentType, String fileName,
                                 Integer bizType, Map<String, Object> bizData, Integer delFlag) throws Exception {
        Map<String, String> map = secretAliOSSUtil.uploadFile(inputStream, contentType, fileName);
        AnnexVO view = saveEntity(map, contentType, fileName, bizType, bizData, delFlag);
        return view;
    }

    @Value("${base.thumbnails.isChangeSize}")
    private boolean isChangeSize;

    @Value("${base.thumbnails.scale}")
    private float scale;

    @Value("${base.thumbnails.outputQuality}")
    private float outputQuality;

    /**
     * 缩放图片，如果业务上传类型不是图片则直接返回数据流
     *
     * @param inputStream
     * @return
     */
    private byte[] changeSize(InputStream inputStream) {
        ByteArrayOutputStream outputStream = null;
        try {
            outputStream = new ByteArrayOutputStream();
            Thumbnails.of(inputStream).scale(scale).outputQuality(outputQuality).toOutputStream(outputStream);
            return outputStream.toByteArray();
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            return null;
        }
    }

    /**
     * 保存实体
     *
     * @param map
     * @param contentType
     * @param fileName
     * @param bizType
     * @param bizData
     * @return
     * @throws Exception
     */
    private AnnexVO saveEntity(Map<String, String> map, String contentType, String fileName,
                               Integer bizType, Map<String, Object> bizData, Integer delFlag) throws Exception {
        AnnexVO view = null;
        if (map != null) {
            SysAnnex annex = new SysAnnex();
            annex.setId(idGenerator.nextId());
            annex.setFileName(fileName);
            annex.setContentType(contentType);
            annex.setStoreLocation(MapUtils.getString(map, "location"));
            annex.setUrl(MapUtils.getString(map, "url"));
            annex.setSort(999);
            annex.setStoreType(1);
            annex.setBizType(bizType);
            annex.setDelFlag(delFlag);
            annex.setCreateTime(LocalDateTime.now());
            annex.setBizData(bizData);
            this.save(annex);
            annex = this.getById(annex.getId());
            view = new AnnexVO();
            BeanUtils.copyProperties(view, annex);
        }
        return view;
    }

    private AnnexVO saveEntity(Map<String, String> map, String contentType, String fileName,
                               Integer bizType, Long bizId, Integer bizCode,
                               Map<String, Object> bizData, Integer delFlag) throws Exception {
        AnnexVO view = null;
        if (map != null) {
            SysAnnex annex = new SysAnnex();
            annex.setId(idGenerator.nextId());
            annex.setFileName(fileName);
            annex.setContentType(contentType);
            annex.setStoreLocation(MapUtils.getString(map, "location"));
            annex.setUrl(MapUtils.getString(map, "url"));
            annex.setSort(999);
            annex.setStoreType(1);
            annex.setBizType(bizType);
            annex.setBizId(bizId);
            annex.setBizCode(bizCode);
            annex.setDelFlag(delFlag);
            annex.setCreateTime(LocalDateTime.now());
            annex.setBizData(bizData);
            this.save(annex);
            annex = this.getById(annex.getId());
            view = new AnnexVO();
            BeanUtils.copyProperties(view, annex);
        }
        return view;
    }

    /**
     * 根据业务通过接口获取数据
     * 101=营业执照
     * 102=身份证（正）
     * 103=身份证（反）
     * 104=银行卡
     *
     * @param inputStream
     * @param bizType
     * @return
     */
    private Map<String, Object> getBizData(InputStream inputStream, Integer bizType) throws Exception {
        String str64 = FileUtil.getInputStreamBase64(inputStream);
        switch (bizType) {
            case 101: // 营业执照
                return getBusinessLicense(str64);
            case 102: // 身份证（正）
                return getIdCard(str64, true);
            case 103: // 身份证（反）
                return getIdCard(str64, false);
            case 104: // 银行卡
                return getBankCard(str64);
            default:
                return null;
        }
    }

    /**
     * 通过api获取身份证信息
     *
     * @param str64
     * @param isFace
     * @return
     * @throws Exception
     */
    private Map<String, Object> getIdCard(String str64, boolean isFace) {
        try {
            Map<String, Object> body = new HashMap<>();
            body.put("image", str64);
            if (isFace) {
                body.put("configure", "{\"side\":\"face\"}");
            } else {
                body.put("configure", "{\"side\":\"back\"}");
            }
            String jsonStr = JsonUtil.toJson(body);
            ApiResponse response = idCardClient.syncReq(jsonStr.getBytes(SdkConstant.CLOUDAPI_ENCODING));
            String returnStr = new String(response.getBody(), SdkConstant.CLOUDAPI_ENCODING);
            IdCardVO view = IdCardVO.build(JsonUtil.jsonToMap(returnStr));
            if (view.isValid()) {
                return view;
            } else {
                return null;
            }
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            return null;
        }
    }

    /**
     * 通过api获取银行卡信息
     *
     * @param str64
     * @return
     * @throws Exception
     */
    private Map<String, Object> getBankCard(String str64) {
        try {
            Map<String, Object> body = new HashMap<>();
            body.put("image", str64);
            String jsonStr = JsonUtil.toJson(body);
            ApiResponse response = bankCardClient.syncReq(jsonStr.getBytes(SdkConstant.CLOUDAPI_ENCODING));
            String returnStr = new String(response.getBody(), SdkConstant.CLOUDAPI_ENCODING);
            BankCardVO view = BankCardVO.build(JsonUtil.jsonToMap(returnStr));
            if (view.isValid()) {
                return view;
            } else {
                return null;
            }
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            return null;
        }
    }

    /**
     * 通过api获取营业执照信息
     *
     * @param str64
     * @return
     * @throws Exception
     */
    private Map<String, Object> getBusinessLicense(String str64) {
        try {
            Map<String, Object> body = new HashMap<>();
            body.put("image", str64);
            String jsonStr = JsonUtil.toJson(body);
            ApiResponse response = businessLicenseClient.syncReq(jsonStr.getBytes(SdkConstant.CLOUDAPI_ENCODING));
            String returnStr = new String(response.getBody(), SdkConstant.CLOUDAPI_ENCODING);
            BusinessLicenseVO view = BusinessLicenseVO.build(JsonUtil.jsonToMap(returnStr));
            if (view.isValid()) {
                return view;
            } else {
                return null;
            }
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            return null;
        }
    }


}
